跳转至

SOP-001:小樱桃语音交互体系

最后变更:2026-09-10 14:44

语音设备日常运维 · VAD 参数 · 音量增益 · PowerMem · 角色设定 · 故障排查

服务器拓扑

┌─────────────┐     WebSocket      ┌──────────────────┐
│   ESP32     │ ─────────────────→ │  xiaozhi-esp32-  │
│   (音箱)    │    ws://:18000     │  server (docker)  │
└─────────────┘                    └────────┬─────────┘
                                     ┌──────┴──────┐
                                     │  open-xiaoai │
                                     │  -xiaozhi    │
                                     │  桥接器      │
                                     └──────┬──────┘
                                     ┌──────────────┐
                                     │  DeepSeek API │
                                     │ deepseek-flash│
                                     │  (V4.1-Flash)│
                                     └──────────────┘

小樱桃容器内还包含:
  ┌─────────────────────────────────────────┐
  │ xiaozhi-esp32-server (container)        │
  │  ├─ app.py → WebSocket :18000           │
  │  ├─ app.py → subprocess.Popen           │
  │  │   └─ powermem-server :8848           │
  │  └─ powermem_dev.db ← 唯一记忆 DB      │
  └─────────────────────────────────────────┘

关键地址: - 服务器 IP:192.168.31.27 - WebSocket 端口:18000 - HTTP 端口:18003 - Dashboard 端口:8847 - 容器名:xiaozhi-esp32-server

参数表

VAD(语音活动检测)

参数 server bridge 说明
threshold 0.30 0.10 进入说话的门槛
threshold_low 0.20 0.01 退出说话的门槛(保持 hysteresis 差距)
min_speech_duration 250ms 最短语音触发时长
min_silence 1200ms 1200ms 静默多久算完(两端统一)
boost 10(代码未读取,无效参数 配置中 +10,但 bridge 代码无任何引用
frame_window_threshold 3 连续几帧达到 threshold 才算开始说话(防误触)

VAD 双阈值(hysteresis):threshold > threshold_low,防止在边界处反复触发/关闭。 桥接器 VAD 默认值见 /app/.venv/.../silero_vad/__init__.py,配置覆盖见 /app/config.py。 服务器 VAD 默认值见 silero.py:27(threshold=0.5, threshold_low=0.2, min_silence=1000),.config.yaml 覆盖为当前运行值。 ⚠️ server 修改方式:改 .config.yamldocker restart xiaozhi-esp32-server ⚠️ bridge 修改方式:改 config.pydocker restart open-xiaoai-xiaozhi

唤醒与超时

参数 bridge server 说明
唤醒前动作 "嗯!" + 0.5s 缓冲 bridge before_wakeup,KWS 唤醒时播放缓冲
小爱拦截 abort_xiaoai + sleep 2.0s 小爱正在说话时拦截并等待
唤醒后动作 "小小苏,拜拜" bridge after_wakeup,退出时播报
唤醒词 你好小樱桃/小樱桃你好/呼叫小樱桃/召唤小樱桃 支持 4 个变体
唤醒超时 20s 超过此时间未说话自动退出
WS 超时 120s(close_connection_no_voice_time 无语音多久断连
TTS 超时 15s(tts_timeout TTS 生成超时上限

音量

# 位置 增益 实现方式
1 桥接器 codec.py → write_audio() audioop.mul(pcm_data, 2, 1.5) 约 +3.5dB PCM 采样值相乘
2 桥接器 config.py boost: 10 无效参数(代码未读取此字段)
3 服务器 util.py → audioop.mul(raw_data, 2, 3) 3x = +9.5dB PCM 采样值相乘
4 服务器 .config.yaml gain: 10 CosyVoice2 API 增益参数(满值)

#1、#3、#4 是同一组,一起调小樱桃和小爱的音量一致性。

2(boost)是无效参数,代码未读取,修改无任何效果。

模型配置

参数
LLM (主) deepseek-flash(DeepSeek V4.1-Flash)via DeepSeek api.deepseek.com(2026-09-10 起,原 deepseek-chat 别名 → 正式名,同一模型)
LLM (备用) glm-4.5-air via 智谱 open.bigmodel.cn(原主,降级备用,2026-08-08 起)
Embedding embedding-3 via 智谱 open.bigmodel.cn(1536 维,保持)
TTS CosyVoice2-0.5B:anna via 硅基流动 siliconflow(gain=10, response=wav)
ASR FunASR SenseVoiceSmall(容器内本地,模型 data/models/SenseVoiceSmall
PowerMem selected_module=powermem(启用量化记忆,向量库=sqlite)

角色设定(小樱桃)

  • 身份: 苏子桐的 AI 小伙伴(不是助手、不是老师)
  • 年龄定位: 永远比苏子桐大 2 岁(她 6 岁我 8 岁,她 10 岁我 12 岁)
  • 关系: 朋友/玩伴,不是管教者
  • 语气: 童真、好奇、偶尔调皮
  • 原则: 引导表达 > 直接给答案。不主动说教
  • 知识边界: 通过 PowerMem 知道苏子桐的经历,但不假装全知

prompt 四段结构:模板规则 + few-shot + 动态上下文(时间/记忆) + 聊天历史

用户画像 — 双层注入

总体结构

每次对话时,系统向 LLM 注入两层用户画像:

<memory>
  <stable_profile>         ← 你维护的权威事实
    [按话题匹配注入]
  </stable_profile>

  <dynamic_profile>        ← PowerMem 自动学习
    [AI 从对话中提取的印象]
  </dynamic_profile>

  优先级:stable 高于 dynamic,冲突时以 stable 为准
</memory>

稳定层(你维护)

  • 来源: stable_profile.txt(位于服务器数据目录)
  • 格式: 6 个 <topic> 话题块 + _default 兜底块
  • 基本信息(年龄、身高、体重)
  • 学习(RAZ、英语、游泳)
  • 生活作息(睡眠时间)
  • 兴趣爱好(动画角色)
  • 社交(家人、手表、微信)
  • _default(简短概要,话题不命中时兜底)
  • 匹配方式: 用户消息关键词 → 命中对应话题块
  • 聊 RAZ → 只注入"学习"块,省 token
  • 聊叶罗丽 → 只注入"兴趣爱好"块
  • 都不命中 → 仅注入 _default
  • 更新方式: 直接覆盖文件,下一句话立即生效(无需重启容器)
  • 备份: 同目录 stable_profile.txt.bak
  • 路径: /opt/xiaozhi-esp32-server/data/stable_profile.txt
  • 权限: 同目录其他配置文件一致

动态层(AI 自动)—— PowerMem 记忆系统

PowerMem 负责对话时的语义记忆搜索对话记忆保存。容器内自洽运行,Dashboard 同容器内 :8848。

保存策略(save_memory)

对话完成后,只提取苏子桐的最后一句话,清洗后存入:

  1. 提取:只取最后一条 user 消息(不存小樱桃的回复)
  2. 清洗:去"苏子桐说:"前缀 → 去 emoji → 去末尾标点(。,!?)→ 去前后空白
  3. 过滤:退出意图(拜拜/再见/bye/88)、短消息(<3字)、无意义词(好的/嗯/哦)→ 不存
  4. 去重:MD5 去重,重复内容不写入
  5. 写入UserMemory.add(infer=False, metadata={source, user})memories
  6. payload:完整 JSON(含 data 原文、hash、user_id、metadata)
  7. vector:JSON 数组字符串 "[]"(SDK 自动维护 embedding)
  8. fulltext_content:清洗后的文本(供语义搜索召回)

搜索策略(query_memory)

用户说话时实时触发:

  1. 搜索当前用户消息的语义近邻记忆(limit=30)
  2. 同时注入 user_profiles 表(用户画像动态层)
  3. 搜索结果格式化后填入 <dynamic_profile> 标签 → LLM prompt
  4. 日志:QUERY_DEBUG 可见搜索结果数量、首条内容

Dashboard(Web 管理界面)

属性
地址 http://192.168.31.27:8848/dashboard/
启动方式 容器启动时自动启动 —— app.pysubprocess.Popen(["powermem-server", "--host", "0.0.0.0", "--port", "8848"])
关联进程 容器内 powermem-server 进程(和 PowerMem SDK 同读一个 DB 文件)
数据源 容器内 /opt/xiaozhi-esp32-server/data/powermem_dev.db唯一 DB,无需同步到宿主)
API 端点 /api/v1/memories / /api/v1/memories/stats / /api/v1/memories/search / /api/v1/memories/timeline
检查项 curl localhost:8848/api/v1/memories/stats 返回 total_memories ② 页面 timeline 有事件条 ③ 记忆条数 > 0

架构变化: v0.4 前 Dashboard 跑在宿主机,需 cron 同步 DB(因 ZOS FUSE 隔离 bind mount 不可行)。v0.4 起 Dashboard 移入容器,和 PowerMem 同读一个 DB 文件,零同步零延迟。旧宿主看门狗已停用。

持久化:powermem_dev.db

容器内 /opt/xiaozhi-esp32-server/data/powermem_dev.db唯一的 PowerMem 数据库。宿主同路径文件 /opt/data/xiaozhi-server/docker/xiaozhi-server/data/powermem_dev.db 是 Dashboard 的只读副本(容器重建后由 docker cp 初始化,后续同步仅通过日记 cron 的 docker cp 步骤)。

  • 对话记忆:save_memory 直接写入容器内 DB
  • 日记记忆:容器内 pm_diary_sync.py 写入容器内 DB(每日 9:00 cron)
  • 搜索:PowerMem SDK 读取容器内 DB
  • Dashboard:powermem-server 读取容器内 DB
  • 宿主张贴板:仅在容器重建后需要 docker cp 初始化

持久化说明

修改桥接器参数 → 直接改 /app/config.py + 重启容器

/app/config.py           ← 桥接器参数(VAD/唤醒词),bind mount 挂载
/app/xiaozhi/services/audio/codec.py   ← 音量增益 audioop.mul,直接改

服务器端:

/opt/xiaozhi-esp32-server/data/.config.yaml       ← 服务器参数
/opt/xiaozhi-esp32-server/data/init_and_run.py    ← 启动补丁(音量增益 + 模糊匹配 + 记忆保存)
/opt/xiaozhi-esp32-server/core/utils/util.py       ← 音量增益(init_and_run 启动时打补丁)
/opt/xiaozhi-esp32-server/core/connection.py       ← 模糊匹配 + 记忆保存(init_and_run 启动时打补丁)
/opt/xiaozhi-esp32-server/core/providers/memory/powermem/powermem.py ← save_memory 逻辑(pm_v12.py)
/opt/xiaozhi-esp32-server/app.py                   ← 主入口,启动时自动拉起 powermem-server (Dashboard :8848)
/opt/xiaozhi-esp32-server/data/pm_diary_sync.py    ← 日记→记忆注入脚本(每日9:00 cron)
/opt/xiaozhi-esp32-server/data/obsidian/小樱桃日记.md ← 日记源文件(cron 从 Obsidian 同步到容器)

故障排查

症状 可能原因 检查
没声音 VAD 门槛太高 / 音量太低 检查 threshold 和 gain
破音 总增益过高 总增益 ≥ +18dB 时有削顶风险
回答太长被截断 VAD min_silence 太长 检查 server 和 bridge 的 min_silence
反复触发/频繁打断 threshold == threshold_low hysteresis 双阈值需保持差距
沉默不答 TTS 超时 / ASR 超时 检查日志中的 timeout
答非所问/说胡话 PowerMem 污染 / model 不可用 检查 LLM 响应 + API key · → 见 powermem-save-fix skill
名字念错/叫不对 ASR 听歪 + 模糊匹配没兜住 缺的角色名加到 hotwords.txt(122条)
Dashboard 页面空白 Dashboard 未启动 / 端口冲突
Dashboard Timeline 无事件 记忆为日记注入(直接 SQLite 写入),对话记忆才有 Timeline 事件

ASR 热词与模糊匹配

Hotwords + 拼音模糊匹配,两层兜底。

第一层:后处理模糊匹配(connection.py _fuzzy_correct_names)

ASR 完成后、LLM 处理前,接管文本做角色名修正:

策略 匹配条件 示例
精确匹配 seg == hw 海绵宝宝 → ✅
编辑距离 ≤ 1 2字名首字必须相同(防误匹配) 赛咯赛罗 ✅ / 和迪巴迪
拼音完全相同 编辑距离 ≥ 2 但拼音去声调后一致 赛箩赛罗 ✅(同音) / 紫曰紫悦

拼音匹配的额外收益: 多音字、口齿不清、同音别字全部覆盖。 防误匹配:2字名编辑距离=1时首字不同不匹配(防 和迪→巴迪)。

数据源:hotwords.txt

  • 路径:/opt/xiaozhi-esp32-server/data/hotwords.txt
  • 当前:122 个名字(奥特曼/小马宝莉/叶罗丽/迪士尼等角色)
  • 特点:文件式动态加载,无需重启容器,改完即生效
  • 更新方式:直接编辑文件或告诉我加角色名

文件位置

文件 用途 持久化方式
connection.py 实时代码(_fuzzy_correct_names init_and_run.py 启动补丁
init_and_run.py 容器重启后补丁代码 挂载在容器内
hotwords.txt 角色名列表(122条) bind mount 挂载

变更日志

铁律:改参数必更新

任何涉及小樱桃代码、配置、参数的变更,必须同步更新本 SOP 对应章节。 不改 SOP-001 视为变更未完成。

检查清单: - [ ] VAD 参数变了? → 更新参数表 - [ ] 音量增益变了? → 更新参数表 - [ ] 模型/API变了? → 更新模型配置 - [ ] 角色设定变了? → 更新角色设定 - [ ] 排查流程新增了? → 更新故障排查 - [ ] 版本日志追加了一笔

日期 版本 变更
2026-07-06 v1 初版,基线参数固化
2026-07-08 v2 新增 ASR 热词拼音模糊匹配章节
2026-07-08 v3 修正音量参数(1~4# 分组)、VAD 值对齐实际运行值、更新 TTS/CosyVoice2 模型配置、更新持久化文件路径
2026-07-08 v4 桥接器 VAD 初调:threshold 0.10→0.20,threshold_low 新增0.15,min_speech 250→300ms
2026-07-08 v5 稳定状态:VAD 全部还原(0.10/0.01/250ms),仅留 min_silence=1000ms;唤醒改用"嗯!"+0.5s缓冲;TTS key 修正(硅基流动);prompt 讲故事不中断;SOP 记录 VAD 参数和唤醒流程
2026-07-09 v6 用户画像重构:双层注入(stable_profile.txt + PowerMem),年龄定位修正为"永远比苏子桐大2岁",反哺机制从向量切片改为文件直接注入
2026-07-10 v8 PowerMem 三层加固:①PATCH7(去重+过滤无意义词+直写 SQLite)②中修(对话后刷新 user_profiles 表)③深修(修正 embedder 配置字段名 + 清洗嵌入文本去前缀表情 + 真实 1536 维向量写入)。语义搜索从空向量升级为智谱 embedding-3 全链路。
2026-07-11 v9 PowerMem 架构重构:Dashboard 移入容器内 :8848(app.py subprocess.Popen 自动启停),和 PowerMem SDK 同读一个 DB,零同步零延迟。save_memory 改为只存苏子桐消息(不存小樱桃回复),清洗+过滤退出意图。日记注入脚本容器内跑(pm_diary_sync.py,每日9:00 cron)。旧宿主看门狗停用。
2026-07-18 v10 大修:#3 server PCM 2.0x→3.16x(+6→+10dB);#4 TTS gain 3→10(满值);#1 bridge PCM 补丁重加(1.5x);PowerMem 401 修(validation_alias 别名传 base_url);记忆去重 59→38;prompt 加【基本原则】「不知道就说不知道」防护;use_powermem 初始化失败改 False
2026-08-08 v11 LLM 切换:主对话 LLM glm-4.5-air(智谱)→ deepseek-chat(DeepSeek),因智谱余额停用;PowerMem LLM 同步换 DeepSeek;embedding-3 保持智谱。已固化 xiaozhi-server:stable(sha 632c1a5)
2026-09-10 v12 模型名对齐:deepseek-chat(弃用别名)→ deepseek-flash(DeepSeek V4.1-Flash),两处(LLM.DeepSeekLLM.model_name + powermem.llm.config.model)已改并重启验证;embedding-3 保持智谱。已固化 xiaozhi-server:stable

相关文档

方向 链接
🔗 输入 SOP-004 融合画像更新(反哺来源) · SOP-003 雷达扫描与推送(日志归档)
🔗 输出 最新画像(反哺目标)
🔧 运维 xiaozhi-ops skill(小樱桃全链路运维 · 修复历史 · 故障排查)
🔧 系统 digital-twin-ops skill(数字孪生系统全链路 · 健康检查 · SOP审计)
🔧 修复 powermem-save-fix skill(记忆保存失败/不可见排查)
📝 变更 版本日志